#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import sys
import pwd

from enum import Enum

from dws.commons.common import check_sh_cmd_param, log, dws_exec_cmd_by_type
from common.util.check_utils import is_valid_id
from dws.commons.parse_param import parse_param_with_jsonschema
from dws.commons.function_tool import log_start
from dws.resource.dws_common import DwsCommon
from common.common import execute_cmd, exter_attack, output_result_file
from common.const import ExecuteResultEnum

from dws.commons.const import DwsRetEnum
from dws.commons.error_code import DwsErrorCode
from dws.resource.dws_cluster import DwsCluster
from dws.resource.dws_database import DwsDatabase
from dws.resource.dws_schema import DwsSchema, DwsSchemaParam
from dws.resource.dws_table import DwsTable, DwsTableParam


class FuncTypeEnum(str, Enum):
    CHECK = "check"
    CLUSTER = "cluster"
    SCHEMA = "schema"
    TABLE = "table"


class DwsResource:

    def __init__(self, pid=""):
        self._pid = pid
        self._user_name = ""
        self._env_file = ""
        self._exe_name = "gs_om"
        self._query_name = ""
        self._query_condition = {}
        self._check_app_out = {
            "code": ExecuteResultEnum.INTERNAL_ERROR,
            "bodyErr": 0,
            "message": "Check application failed.",
            "bodyErrParams": []
        }
        self._query_cluster_out = {
            "id": "",
            "name": "",
            "type": "DataBase",
            "subType": "DWS-cluster",
            "endpoint": "",
            "role": 3,
            "nodes": [],
            "extendInfo": ""
        }
        self._list_app_out = {"resourceList": [], "pageNo": 0, "pageSize": 0, "pages": 0, "total": 0}
        # 保存主机名字和 ip信息 dict
        self._cluster_agents = None
        self._host_agents = None

    @staticmethod
    @log_start()
    def check_node_env(user_name, env_file, pid=""):
        """
        公共接口: 检查当前机器是否侵入式部署的Agent, 会根据不同的报错返回不同的枚举值
        """
        # 检查是否是root用户
        if user_name == "root":
            log.error(f"Cluster user cannot be root, PID: {pid}.")
            return DwsErrorCode.USER_AUTH_ENV_PATH_ERROR
        try:
            pwd.getpwnam(user_name)
        except Exception:
            log.error(f"Param invalid, user_name.")
            return DwsErrorCode.USER_NO_EXIST_CLUSTER

        if not os.path.exists(env_file):
            log.error("Env file not exists.")
            return DwsErrorCode.USER_AUTH_ENV_PATH_ERROR
        try:
            if pwd.getpwuid(os.stat(env_file).st_uid).pw_name != user_name:
                log.error(f"Param env file invalid, not belong user")
                return DwsErrorCode.USER_AUTH_ENV_PATH_ERROR
        except Exception:
            log.error(f"Param env file invalid, not exits")
            return DwsErrorCode.USER_AUTH_ENV_PATH_ERROR

        # 检查用户是否存在
        return_code, _, err_info = execute_cmd(f"id {user_name}")
        if return_code != DwsRetEnum.SUCCESS:
            log.error(f"Query user_name error with return: {err_info}, PID: {pid}.")
            return DwsErrorCode.USER_NO_EXIST_CLUSTER

        # 切换用户，加载环境变量，并查找gs_om程序
        ret, output_info = dws_exec_cmd_by_type(user_name, env_file, "which gs_om")
        if not ret:
            log.error(f"Query gs_om error with return: {output_info}, PID: {pid}.")
            return DwsErrorCode.USER_AUTH_ENV_PATH_ERROR

        return DwsErrorCode.NO_ERR

    @staticmethod
    @exter_attack
    def run_main(func_type, pid, stdin_param):
        err_out_obj = {"code": ExecuteResultEnum.INTERNAL_ERROR, "bodyErr": 0, "message": ""}
        try:
            total_param = parse_param_with_jsonschema(pid)
        except Exception as e:
            log.exception(f"Failed to parse param file, PID: {pid}, err: {e}.")
            return False

        resource = DwsResource(pid)
        if not resource.parse_param_detail(total_param, func_type, stdin_param):
            log.error(f"Failed to parse detail param, PID: {pid}.")
            err_out_obj["message"] = "Failed to parse detail param."
            output_result_file(pid, err_out_obj)
            return False

        func_dict = {
            FuncTypeEnum.CHECK: resource.check_application,
            FuncTypeEnum.CLUSTER: resource.query_cluster,
            FuncTypeEnum.SCHEMA: resource.list_schema_v2,
            FuncTypeEnum.TABLE: resource.list_table_v2
        }

        if func_type not in func_dict:
            log.exception(f"Invalid func_type: {func_type}, PID: {pid}.")
            return False

        ret, out_obj = func_dict.get(func_type)()
        output_result_file(pid, out_obj)
        return ret

    @log_start()
    def _is_intrusive(self):
        """
        函数功能： 判断是不是侵入式部署
        """
        return False if self._host_agents else True

    @log_start()
    def _check_is_need_host_agent(self) -> bool:
        """
        函数功能：8.0.0 不支持 非侵入式 部署
        return:  Bool
        """
        if not self._is_intrusive() and "8.0.0" in DwsCluster(self._user_name, self._env_file).get_cluster_version():
            return True
        return False

    @log_start()
    def check_cluster_info(self, cluster_node_info, intrusive):
        """
        函数功能：判断注册的集群信息的合法性
        """
        log.info(f"Mode: {intrusive}")

        if not cluster_node_info:
            log.error("Invalid param cluster_node_info.")
            return DwsErrorCode.UNKNOWN_ERR

        cluster_info = DwsCluster.get_all_cluster_node_ip(self._user_name, self._env_file)
        if not cluster_info:
            log.error("Failed to get_all_cluster_node_ip.")
            return DwsErrorCode.UNKNOWN_ERR

        err_cluster_info = []
        right_cluster_info = []
        for single_host_ips in cluster_node_info.split(';'):
            not_found = True
            for single_cluster_ip in cluster_info:
                if single_cluster_ip in single_host_ips:
                    right_cluster_info.append(single_host_ips.split(',')[0])
                    not_found = False
                    break
            if not_found:
                # 没找到 集群信息
                err_cluster_info.append(single_host_ips.split(',')[0])

        # 下发的集群节点 都不是本机集群节点
        if not right_cluster_info:
            log.error("Failed to right_cluster_info.")
            return DwsErrorCode.UNKNOWN_ERR

        if err_cluster_info:
            self._check_app_out['bodyErrParams'] = [','.join(right_cluster_info), ','.join(err_cluster_info)]
            self._check_app_out["bodyErr"] = DwsErrorCode.SELECT_CLUSTER_NODE_NO_BELONG_SAME_CLUSTER
            return DwsErrorCode.SELECT_CLUSTER_NODE_NO_BELONG_SAME_CLUSTER
        slect_node_set = set(cluster_node_info.replace(';', ',').split(','))
        if intrusive and not cluster_info.issubset(slect_node_set):
            log.error("Cluster info err.")
            return DwsErrorCode.CHECK_NEED_ADD_CLUSTER_NODE_OR_HOST_AGENT

        all_cn_ip_set = DwsCommon(self._user_name, self._env_file).get_all_cn_hostip()
        if not intrusive and all_cn_ip_set.isdisjoint(slect_node_set):
            log.error("No node with CN instance selected.")
            return DwsErrorCode.NO_CN_INSTANCE_IN_SELECT_CLUSTER_NODES

        return DwsErrorCode.NO_ERR

    def parse_param_detail(self, total_param, func_type, stdin_param):
        """
        从total_param解析出详细的参数
        """
        app_info = {}
        # 通过ListApplicationResourceV2接口传递进来的参数需要特殊处理
        if func_type in [FuncTypeEnum.SCHEMA, FuncTypeEnum.TABLE]:
            app_info_list = total_param.get("applications", [])
            if app_info_list:
                app_info = app_info_list[0]
                self._user_name = stdin_param.get(f"applications_0_auth_authKey_{self._pid}")
        else:
            app_info = total_param.get("application", {})
            self._user_name = stdin_param.get(f"application_auth_authKey_{self._pid}")

        extend_info = app_info.get("extendInfo", {})
        self._env_file = os.path.realpath(extend_info.get("envFile")) if extend_info.get("envFile") else ""
        if not check_sh_cmd_param(self._user_name) or not check_sh_cmd_param(self._env_file):
            log.error(f"Param invalid, env_file: {self._env_file}, PID: {self._pid}.")
            return False
        self._cluster_agents = extend_info.get("clusterAgent")
        self._host_agents = extend_info.get("hostAgent")

        if func_type in [FuncTypeEnum.SCHEMA, FuncTypeEnum.TABLE]:
            self._query_name = app_info.get("name", "")
            if not self._query_name:
                log.error(f"Param query_name is invalid, PID: {self._pid}.")
                return False
            self._query_condition = total_param.get("condition", {})

        log.info(f"Success to get param from file, env_file: {self._env_file}, " \
                 f"query_name: {self._query_name}, PID: {self._pid}.")
        return True

    def fill_detail_param(self, err_code):
        """
        根据错误码填充具体的参数信息
        """
        if err_code == DwsErrorCode.USER_NO_EXIST_CLUSTER:
            self._check_app_out["bodyErrParams"] = [self._user_name]
        elif err_code == DwsErrorCode.USER_AUTH_ENV_PATH_ERROR:
            self._check_app_out["bodyErrParams"] = [self._env_file, self._user_name]

    @exter_attack
    def check_application(self):
        """
        通用接口: 检查集群
        """
        check_ret = DwsResource.check_node_env(self._user_name, self._env_file, self._pid)
        if check_ret != DwsErrorCode.NO_ERR:
            log.error(f"Check node environment failed, PID: {self._pid}.")
            self._check_app_out["bodyErr"] = check_ret
            self.fill_detail_param(check_ret)
            return False, self._check_app_out


        if self._check_is_need_host_agent():
            self._check_app_out["bodyErr"] = DwsErrorCode.CHECK_IS_NEED_HOST_AGENT
            self._check_app_out['bodyErrParams'].append('8.0.0')
            log.error(f"8.0.0 version not support choose host agent, PID: {self._pid}")
            return False, self._check_app_out

        check_ret = self.check_cluster_info(self._cluster_agents, self._is_intrusive())
        if check_ret != DwsErrorCode.NO_ERR:
            self._check_app_out["bodyErr"] = check_ret
            log.error(f"Check choose nodes info failed, PID: {self._pid}")
            return False, self._check_app_out

        self._check_app_out["code"] = ExecuteResultEnum.SUCCESS
        self._check_app_out["message"] = "Check application success."
        return True, self._check_app_out

    @exter_attack
    def query_cluster(self):
        """
        通用接口: 查询集群信息
        """
        check_ret = DwsResource.check_node_env(self._user_name, self._env_file)
        if check_ret != DwsErrorCode.NO_ERR:
            log.error(f"Check node environment failed, PID: {self._pid}.")
            self._check_app_out["bodyErr"] = check_ret
            self.fill_detail_param(check_ret)
            return False, self._check_app_out

        cluster = DwsCluster(self._user_name, self._env_file, self._pid)
        self._query_cluster_out["nodes"] = cluster.get_node_info()
        database = DwsDatabase(self._user_name, self._env_file, self._pid)
        dwscommon = DwsCommon(self._user_name, self._env_file)
        node_list = ",".join(list(dwscommon.get_all_cn_hostip()))
        self._query_cluster_out["extendInfo"] = {"databases": database.get_all_databases(), "cn_nodes": node_list}
        return True, self._query_cluster_out

    @exter_attack
    def list_schema_v2(self):
        """
        通用接口: 分页查询schema信息
        """
        check_ret = DwsResource.check_node_env(self._user_name, self._env_file)
        if check_ret != DwsErrorCode.NO_ERR:
            log.error(f"Check node environment failed, PID: {self._pid}.")
            self._check_app_out["bodyErr"] = check_ret
            self.fill_detail_param(check_ret)
            return False, self._check_app_out

        schema_param = DwsSchemaParam(self._user_name, self._env_file, self._query_name, self._query_condition,
                                      self._pid)
        schema = DwsSchema(schema_param)
        if not schema.is_query_success():
            log.error(f"Query schema info failed, PID: {self._pid}.")
            self._check_app_out["exception"] = {
                "code": DwsErrorCode.CANNOT_QUERY_SCHEMA_WHEN_CLUSTER_STATE_UNNORMAL,
                "message": "Query schema info failed"
            }
            return False, self._check_app_out

        self._list_app_out["resourceList"] = schema.get_all_schemas()
        self._list_app_out["pageNo"] = self._query_condition.get("pageNo", 0)
        self._list_app_out["pageSize"] = self._query_condition.get("pageSize", 0)
        self._list_app_out["pages"] = schema.get_total_page()
        self._list_app_out["total"] = schema.get_total_count()

        return True, self._list_app_out

    @exter_attack
    def list_table_v2(self):
        """
        通用接口: 分页查询table信息
        """
        check_ret = DwsResource.check_node_env(self._user_name, self._env_file)
        if check_ret != DwsErrorCode.NO_ERR:
            log.error(f"Check node environment failed, PID: {self._pid}.")
            self._check_app_out["bodyErr"] = check_ret
            self.fill_detail_param(check_ret)
            return False, self._check_app_out

        names = self._query_name.split("/")
        min_count = 2
        db_name = names[0]
        if len(names) < min_count:
            schema_name = ""
        else:
            schema_name = names[1]
        talbe_param = DwsTableParam(self._user_name, self._env_file, db_name, schema_name, self._query_condition,
                                    self._pid)
        talbe = DwsTable(talbe_param)
        if not talbe.is_query_success():
            log.error(f"Query talbe info failed, PID: {self._pid}.")
            self._check_app_out["exception"] = {
                "code": DwsErrorCode.CANNOT_QUERY_SCHEMA_WHEN_CLUSTER_STATE_UNNORMAL,
                "message": "Query table info failed"
            }
            return False, self._check_app_out
        self._list_app_out["resourceList"] = talbe.get_all_tables()
        self._list_app_out["pageNo"] = self._query_condition.get("pageNo", 0)
        self._list_app_out["pageSize"] = self._query_condition.get("pageSize", 0)
        self._list_app_out["pages"] = talbe.get_total_page()
        self._list_app_out["total"] = talbe.get_total_count()

        return True, self._list_app_out


if __name__ == '__main__':
    PARAM_STDIN = None
    for line in sys.stdin:
        PARAM_STDIN = line
        break

    if not PARAM_STDIN:
        log.error(f"Std in err. pid: {sys.argv[2]}")
        sys.exit(1)

    if len(sys.argv) < 3:
        log.error(f"Param count < 3.")
        sys.exit(1)

    if not is_valid_id(sys.argv[2]):
        log.warn(f"pid is invalid")
        sys.exit(1)

    try:
        if not DwsResource.run_main(sys.argv[1], sys.argv[2], json.loads(PARAM_STDIN)):
            log.error(f"Run main failed, func_type: {sys.argv[1]}, pid: {sys.argv[2]}.")
    except Exception as err:
        log.exception(f"Err: {err}, func_type: {sys.argv[1]}, pid: {sys.argv[2]}.")
        sys.exit(1)

    sys.exit(0)
